﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace RoguelikeLib.Maps
{
    internal enum Direction
    {
        Left = -1,
        Right = 1,
        StraightAhead = 0
    }

    internal enum CardinalDirection
    {
        West = 1,
        North = 2,
        East = 3,
        South = 4,
    }

    internal enum Plane
    {
        Horizontal,
        Vertical
    }
    internal struct Point : IEquatable<Point>
    {
        public static Point Zero = new Point(0, 0);
        private readonly int _x;
        private readonly int _y;


        public Point(int x, int y)
        {
            _x = x;
            _y = y;
        }

        public int X
        {
            get { return _x; }
        }

        public int Y
        {
            get { return _y; }
        }

        public override int GetHashCode()
        {
            int hash = 17;
            hash = hash * 31 + _x;
            hash = hash * 31 + _y;
            return hash;
        }

        public override bool Equals(object other)
        {
            return other is Point && Equals((Point)other);
        }

        public bool Equals(Point other)
        {
            return _x == other.X && _y == other.Y;
        }
    }
    class Corridor
    {
        CardinalDirection GetCardinalDirection(CardinalDirection previousDirection, Direction directionToTurn)
        {
            int newDirection = (int)previousDirection + (int)directionToTurn;
            if (newDirection == 0)
                newDirection = 4;
            else if (newDirection == 5)
                newDirection = 1;

            return (CardinalDirection)newDirection;
        }

        internal class Section
        {
            public int SourceX { get; set; }
            public int SourceY { get; set; }
            public int EndX { get; set; }
            public int EndY { get; set; }
            public int Left { get { return SourceX < EndX ? SourceX : EndX; } }
            public int Right { get { return SourceX > EndX ? SourceX : EndX; } }
            public int Top { get { return SourceY < EndY ? SourceY : EndY; } }
            public int Bottom { get { return SourceY > EndY ? SourceY : EndY; } }
            public int Length { get; set; }
            public Direction Direction { get; set; }
            public Plane Plane { get { return SourceX == EndX ? Plane.Vertical : Plane.Horizontal; } }
        }

        private readonly StandardDungeon _dungeon;
        public StandardDungeon Dungeon
        {
            get { return _dungeon; }
        }

        private readonly List<Section> _sections = new List<Section>();
        public List<Section> Sections
        {
            get { return _sections; }
        }
        private readonly List<Room> _rooms = new List<Room>();
        public List<Room> Rooms
        {
            get { return _rooms; }
        }

        private int width = 1;
        private int _sourceX, _sourceY, _endX, _endY;
        private CardinalDirection _endDirection;
        private Corridor(StandardDungeon dungeon, int x, int y)
        {
            _sourceX = x;
            _sourceY = y;
            _dungeon = dungeon;
            int numberOfSections = Game.Roll(3, 1, 4);
            for (int i = 0; i < numberOfSections; i++)
                Sections.Add(new Section() { Direction = PickDirection(), Length = Game.Roll(4, 0, 3) + 2 });
        }

        public bool AddRooms()
        {
            int numberOfRooms = Game.Roll(1, 1, 3);
            Room room = null;
            for (int attempts = 0; attempts < 3; attempts++)
            {
                room = Room.CreateRoom(Dungeon, _endX, _endY, GetCardinalDirection(_endDirection, PickDirection()));
                if (room != null && !IntersectsWithRoom(room))
                    break;
            }
            if (room == null)
                return false;

            Rooms.Add(room);

            for (int i = 0; i < numberOfRooms - 1; i++)
            {
                // TODO: Create rooms at random points on corridor
                var section = Sections[Game.Roll(1, 0, Sections.Count)];
                room = Room.CreateRoom(Dungeon, Game.Roll(1, section.SourceX, section.EndX), Game.Roll(1, section.SourceY, section.EndY),
                                       section.SourceX == section.EndX ? PickHorizontalCardinalDirection() : PickVerticalCardinalDirection());
                if (room == null || IntersectsWithRoom(room))
                    continue;
                Rooms.Add(room);
            }
            return true;
        }

        public bool AddToMap()
        {
            //int x = _sourceX, y = _sourceY;
            var previousDirection = CardinalDirection.North;

            foreach (var section in Sections)
            {
                var cardinalDirection = GetCardinalDirection(previousDirection, section.Direction);
                previousDirection = cardinalDirection;
                for (int y = section.Top; y <= section.Bottom; y++)
                    for (int x = section.Left; x <= section.Right; x++)
                        Dungeon.Map[y][x].Type = RObjects.StoneFloor;

                //    for (int i = 0; i < section.Length; i++)
                //    {
                //        Dungeon.Map[y][x].Type = RObjects.StoneFloor;
                //        switch (cardinalDirection)
                //        {
                //            case CardinalDirection.West:
                //                x--;
                //                break;
                //            case CardinalDirection.North:
                //                y--;
                //                break;
                //            case CardinalDirection.East:
                //                x++;
                //                break;
                //            case CardinalDirection.South:
                //                y++;
                //                break;
                //        }

                //    }
                //Dungeon.Map[y][x].Type = RObjects.StoneFloor;

            }
            return true;
        }

        public Direction PickDirection()
        {
            int direction = Game.Roll(1, 1, 6);
            if (direction < 3)
                return Direction.StraightAhead;
            if (direction < 5)
                return Direction.Left;
            return Direction.Right;
        }

        bool IsClear()
        {
            int x = _sourceX, y = _sourceY;
            var previousDirection = CardinalDirection.North;

            Dictionary<Point, Section> pointToSectionMap = new Dictionary<Point, Section>();
            HashSet<Section> sectionsToRemove = new HashSet<Section>();
            for (int index = 0; index < Sections.Count; index++)
            {
                var section = Sections[index];
                // There is no need to check this section if we're getting rid of it
                if (sectionsToRemove.Contains(section))
                    continue;
                var cardinalDirection = GetCardinalDirection(previousDirection, section.Direction);
                previousDirection = cardinalDirection;
                section.SourceX = x;
                section.SourceY = y;

                for (int i = 0; i < section.Length; i++)
                {
                    var point = new Point(x, y);
                    if (!pointToSectionMap.ContainsKey(point))
                        pointToSectionMap.Add(point, section);
                    else
                    {
                        // Remove sections in between these two
                        var firstSectionAtThisPoint = pointToSectionMap[point];
                        var firstSectionIndex = Sections.IndexOf(firstSectionAtThisPoint);
                        for (int indexToRemove = firstSectionIndex + 1; indexToRemove < index; indexToRemove++)
                        {
                            var sectionToRemove = Sections[indexToRemove];
                            if (!sectionsToRemove.Contains(sectionToRemove))
                                sectionsToRemove.Add(sectionToRemove);
                        }
                        // Shorten section to point
                        if (section.Plane != firstSectionAtThisPoint.Plane)
                        {
                            section.SourceX = x;
                            section.SourceY = y;
                        }
                    }
                    switch (cardinalDirection)
                    {
                        case CardinalDirection.West:
                            x--;
                            break;
                        case CardinalDirection.North:
                            y--;
                            break;
                        case CardinalDirection.East:
                            x++;
                            break;
                        case CardinalDirection.South:
                            y++;
                            break;
                    }
                    if (i == section.Length - 1)
                    {
                        section.EndX = x;
                        section.EndY = y;
                    }
                    if (y < 1 || y >= Dungeon.Map.Length - 1
                        || x < 1 || x >= Dungeon.Map[y].Length - 1
                        || Dungeon.Map[y][x].Type != RObjects.BaseMaterial)
                        return false;
                }
            }
            foreach (var section in sectionsToRemove)
            {
                Sections.Remove(section);
            }
            _endX = x;
            _endY = y;
            _endDirection = previousDirection;
            return true;
        }

        public bool IntersectsWithRoom(Room room)
        {
            List<Point> points;
            return IntersectsWithRoom(room, out points);
        }

        public bool IntersectsWithRoom(Room room, out List<Point> points)
        {
            points=new List<Point>();
            //Point previousPoint = Point.Zero;
            //foreach (var section in Sections)
            //    for (int y = section.Top; y <= section.Bottom; y++)
            //        for (int x = section.Left; x <= section.Right; x++)
            //        {
            //            if (room.ContainsPoint(x, y) && !room.ContainsPoint(previousPoint.X, previousPoint.Y))
            //            {
            //                points.Add(new Point(x, y));
            //                return true;
            //            }
            //            previousPoint = new Point(x, y);
            //        }

            int x = _sourceX, y = _sourceY;
            Point previousPoint = Point.Zero;
            Point point = Point.Zero;
            var previousDirection = CardinalDirection.North;
            bool isFirstPoint = true;
            bool isLastPoint = false;
                
            for (int index = 0; index < Sections.Count; index++)
            {
                if (index == Sections.Count - 1)
                    isLastPoint = true;
                var section = Sections[index];
                var cardinalDirection = GetCardinalDirection(previousDirection, section.Direction);
                previousDirection = cardinalDirection;
                for (int i = 0; i < section.Length; i++)
                {
                    var nextPoint = new Point(x, y);

                    switch (cardinalDirection)
                    {
                        case CardinalDirection.West:
                            x--;
                            break;
                        case CardinalDirection.North:
                            y--;
                            break;
                        case CardinalDirection.East:
                            x++;
                            break;
                        case CardinalDirection.South:
                            y++;
                            break;
                    }
                    if (!isFirstPoint && room.ContainsPoint(point.X, point.Y)
                        && !room.ContainsPoint(previousPoint.X, previousPoint.Y) 
                        && !room.ContainsPoint(nextPoint.X, nextPoint.Y))
                        points.Add(nextPoint);
                    previousPoint = point;
                    point = nextPoint;
                    isFirstPoint = false;
                }
            }
            return points.Any();
        }

        public CardinalDirection PickHorizontalCardinalDirection()
        {
            return Game.Roll(1, 0, 1) == 0 ? CardinalDirection.West : CardinalDirection.East;
        }
        public CardinalDirection PickVerticalCardinalDirection()
        {
            return Game.Roll(1, 0, 1) == 0 ? CardinalDirection.North : CardinalDirection.South;
        }

        public static Corridor FromPoint(StandardDungeon dungeon, int x, int y)
        {
            var corridor = new Corridor(dungeon, x, y);
            if (!corridor.IsClear())
                return null;
            corridor.AddRooms();
            return corridor;
        }

    }
}
